#include "SQFuncs.h"
#include <stdio.h>

// Allow weak algorithms (MD5 only)
#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1

// Crypto++ methods
#include "CryptoPP/aes.h"
#include "CryptoPP/base64.h"
#include "CryptoPP/blowfish.h"
#include "CryptoPP/gzip.h"
#include "CryptoPP/hex.h"
#include "CryptoPP/md5.h"
#include "CryptoPP/ripemd.h"
#include "CryptoPP/sha.h"
#include "CryptoPP/twofish.h"
#include "CryptoPP/whrlpool.h"

extern SQAPI sq;

_SQUIRRELDEF( base64_encode )
{
	SQInteger iArgCount = sq->gettop( v );
	if( iArgCount >= 1 )
	{
		const SQChar * szInput;
		sq->getstring( v, 2, &szInput );

		std::string encodedBase64;
		CryptoPP::StringSource(szInput, true, new CryptoPP::Base64Encoder(new CryptoPP::StringSink(encodedBase64)));

		sq->pushstring( v, encodedBase64.c_str(), -1 );
	}
	else
		sq->pushnull(v);

	return 1;
}

_SQUIRRELDEF( base64_decode )
{
	SQInteger iArgCount = sq->gettop( v );
	if( iArgCount >= 1 )
	{
		const SQChar * szInput;
		sq->getstring( v, 2, &szInput );

		std::string decodedBase64;
		CryptoPP::StringSource(szInput, true, new CryptoPP::Base64Decoder(new CryptoPP::StringSink(decodedBase64)));

		sq->pushstring( v, decodedBase64.c_str(), -1 );
	}
	else
		sq->pushnull(v);

	return 1;
}

_SQUIRRELDEF( MD5 )
{
	SQInteger iArgCount = sq->gettop( v );
	if( iArgCount >= 1 )
	{
		const SQChar * szInput;
		sq->getstring( v, 2, &szInput );

		std::string message(szInput);

		CryptoPP::Weak::MD5 hash;
		byte digest [CryptoPP::Weak::MD5::DIGESTSIZE];

		hash.CalculateDigest( digest, (byte *)szInput, message.length() );
		
		CryptoPP::HexEncoder encoder;
		std::string output;

		encoder.Attach( new CryptoPP::StringSink( output ) );
		encoder.Put( digest, sizeof( digest ) );
		encoder.MessageEnd();

		sq->pushstring( v, output.c_str(), -1 );
	}
	else
		sq->pushnull(v);

	return 1;
}

_SQUIRRELDEF( SHA1 )
{
	SQInteger iArgCount = sq->gettop( v );
	if( iArgCount >= 1 )
	{
		const SQChar * szInput;
		sq->getstring( v, 2, &szInput );

		std::string message(szInput);

		CryptoPP::SHA1 hash;
		byte digest [CryptoPP::SHA1::DIGESTSIZE];

		hash.CalculateDigest( digest, (byte *)szInput, message.length() );
		
		CryptoPP::HexEncoder encoder;
		std::string output;

		encoder.Attach( new CryptoPP::StringSink( output ) );
		encoder.Put( digest, sizeof( digest ) );
		encoder.MessageEnd();

		sq->pushstring( v, output.c_str(), -1 );
	}
	else
		sq->pushnull(v);

	return 1;
}

_SQUIRRELDEF( SHA224 )
{
	SQInteger iArgCount = sq->gettop( v );
	if( iArgCount >= 1 )
	{
		const SQChar * szInput;
		sq->getstring( v, 2, &szInput );

		std::string message(szInput);

		CryptoPP::SHA224 hash;
		byte digest [CryptoPP::SHA224::DIGESTSIZE];

		hash.CalculateDigest( digest, (byte *)szInput, message.length() );
		
		CryptoPP::HexEncoder encoder;
		std::string output;

		encoder.Attach( new CryptoPP::StringSink( output ) );
		encoder.Put( digest, sizeof( digest ) );
		encoder.MessageEnd();

		sq->pushstring( v, output.c_str(), -1 );
	}
	else
		sq->pushnull(v);

	return 1;
}

_SQUIRRELDEF( SHA256 )
{
	SQInteger iArgCount = sq->gettop( v );
	if( iArgCount >= 1 )
	{
		const SQChar * szInput;
		sq->getstring( v, 2, &szInput );

		std::string message(szInput);

		CryptoPP::SHA256 hash;
		byte digest [CryptoPP::SHA256::DIGESTSIZE];

		hash.CalculateDigest( digest, (byte *)szInput, message.length() );
		
		CryptoPP::HexEncoder encoder;
		std::string output;

		encoder.Attach( new CryptoPP::StringSink( output ) );
		encoder.Put( digest, sizeof( digest ) );
		encoder.MessageEnd();

		sq->pushstring( v, output.c_str(), -1 );
	}
	else
		sq->pushnull(v);

	return 1;
}

_SQUIRRELDEF( SHA384 )
{
	SQInteger iArgCount = sq->gettop( v );
	if( iArgCount >= 1 )
	{
		const SQChar * szInput;
		sq->getstring( v, 2, &szInput );

		std::string message(szInput);

		CryptoPP::SHA384 hash;
		byte digest [CryptoPP::SHA384::DIGESTSIZE];

		hash.CalculateDigest( digest, (byte *)szInput, message.length() );
		
		CryptoPP::HexEncoder encoder;
		std::string output;

		encoder.Attach( new CryptoPP::StringSink( output ) );
		encoder.Put( digest, sizeof( digest ) );
		encoder.MessageEnd();

		sq->pushstring( v, output.c_str(), -1 );
	}
	else
		sq->pushnull(v);

	return 1;
}

_SQUIRRELDEF( SHA512 )
{
	SQInteger iArgCount = sq->gettop( v );
	if( iArgCount >= 1 )
	{
		const SQChar * szInput;
		sq->getstring( v, 2, &szInput );

		std::string message(szInput);

		CryptoPP::SHA512 hash;
		byte digest [CryptoPP::SHA512::DIGESTSIZE];

		hash.CalculateDigest( digest, (byte *)szInput, message.length() );
		
		CryptoPP::HexEncoder encoder;
		std::string output;

		encoder.Attach( new CryptoPP::StringSink( output ) );
		encoder.Put( digest, sizeof( digest ) );
		encoder.MessageEnd();

		sq->pushstring( v, output.c_str(), -1 );
	}
	else
		sq->pushnull(v);

	return 1;
}

_SQUIRRELDEF( RIPEMD128 )
{
	SQInteger iArgCount = sq->gettop( v );
	if( iArgCount >= 1 )
	{
		const SQChar * szInput;
		sq->getstring( v, 2, &szInput );

		std::string message(szInput);

		CryptoPP::RIPEMD128 hash;
		byte digest [CryptoPP::RIPEMD128::DIGESTSIZE];

		hash.CalculateDigest( digest, (byte *)szInput, message.length() );
		
		CryptoPP::HexEncoder encoder;
		std::string output;

		encoder.Attach( new CryptoPP::StringSink( output ) );
		encoder.Put( digest, sizeof( digest ) );
		encoder.MessageEnd();

		sq->pushstring( v, output.c_str(), -1 );
	}
	else
		sq->pushnull(v);

	return 1;
}

_SQUIRRELDEF( RIPEMD160 )
{
	SQInteger iArgCount = sq->gettop( v );
	if( iArgCount >= 1 )
	{
		const SQChar * szInput;
		sq->getstring( v, 2, &szInput );

		std::string message(szInput);

		CryptoPP::RIPEMD160 hash;
		byte digest [CryptoPP::RIPEMD160::DIGESTSIZE];

		hash.CalculateDigest( digest, (byte *)szInput, message.length() );
		
		CryptoPP::HexEncoder encoder;
		std::string output;

		encoder.Attach( new CryptoPP::StringSink( output ) );
		encoder.Put( digest, sizeof( digest ) );
		encoder.MessageEnd();

		sq->pushstring( v, output.c_str(), -1 );
	}
	else
		sq->pushnull(v);

	return 1;
}

_SQUIRRELDEF( RIPEMD256 )
{
	SQInteger iArgCount = sq->gettop( v );
	if( iArgCount >= 1 )
	{
		const SQChar * szInput;
		sq->getstring( v, 2, &szInput );

		std::string message(szInput);

		CryptoPP::RIPEMD256 hash;
		byte digest [CryptoPP::RIPEMD256::DIGESTSIZE];

		hash.CalculateDigest( digest, (byte *)szInput, message.length() );
		
		CryptoPP::HexEncoder encoder;
		std::string output;

		encoder.Attach( new CryptoPP::StringSink( output ) );
		encoder.Put( digest, sizeof( digest ) );
		encoder.MessageEnd();

		sq->pushstring( v, output.c_str(), -1 );
	}
	else
		sq->pushnull(v);

	return 1;
}

_SQUIRRELDEF( RIPEMD320 )
{
	SQInteger iArgCount = sq->gettop( v );
	if( iArgCount >= 1 )
	{
		const SQChar * szInput;
		sq->getstring( v, 2, &szInput );

		std::string message(szInput);

		CryptoPP::RIPEMD320 hash;
		byte digest [CryptoPP::RIPEMD320::DIGESTSIZE];

		hash.CalculateDigest( digest, (byte *)szInput, message.length() );
		
		CryptoPP::HexEncoder encoder;
		std::string output;

		encoder.Attach( new CryptoPP::StringSink( output ) );
		encoder.Put( digest, sizeof( digest ) );
		encoder.MessageEnd();

		sq->pushstring( v, output.c_str(), -1 );
	}
	else
		sq->pushnull(v);

	return 1;
}

_SQUIRRELDEF( WHIRLPOOL )
{
	SQInteger iArgCount = sq->gettop( v );
	if( iArgCount >= 1 )
	{
		const SQChar * szInput;
		sq->getstring( v, 2, &szInput );

		std::string message(szInput);

		CryptoPP::Whirlpool hash;
		byte digest [CryptoPP::Whirlpool::DIGESTSIZE];

		hash.CalculateDigest( digest, (byte *)szInput, message.length() );
		
		CryptoPP::HexEncoder encoder;
		std::string output;

		encoder.Attach( new CryptoPP::StringSink( output ) );
		encoder.Put( digest, sizeof( digest ) );
		encoder.MessageEnd();

		sq->pushstring( v, output.c_str(), -1 );
	}
	else
		sq->pushnull(v);

	return 1;
}

SQInteger RegisterSquirrelFunc( HSQUIRRELVM v, SQFUNCTION f, const SQChar* fname, unsigned char ucParams, const SQChar* szParams )
{
	char szNewParams[ 32 ];

	sq->pushroottable( v );
	sq->pushstring( v, fname, -1 );
	sq->newclosure( v, f, 0 ); /* create a new function */

	if ( ucParams > 0 ) 
	{
		ucParams++; /* This is to compensate for the root table */
		
		sprintf( szNewParams, "t%s", szParams );
		sq->setparamscheck( v, ucParams, szNewParams ); /* Add a param type check */
	}

	sq->setnativeclosurename( v, -1, fname );
	sq->newslot( v, -3, SQFalse );
	sq->pop( v, 1 ); /* pops the root table */

	return 0;
}

void RegisterFuncs( HSQUIRRELVM v )
{
	// Base 64 functions
	RegisterSquirrelFunc( v, base64_encode, "base64_encode", 1, "s" );
	RegisterSquirrelFunc( v, base64_decode, "base64_decode", 1, "s" );
	RegisterSquirrelFunc( v, MD5, "MD5", 1, "s" );
	RegisterSquirrelFunc( v, SHA1, "SHA1", 1, "s" );
	RegisterSquirrelFunc( v, SHA224, "SHA224", 1, "s" );
	RegisterSquirrelFunc( v, SHA256, "SHA256", 1, "s" );
	RegisterSquirrelFunc( v, SHA384, "SHA384", 1, "s" );
	RegisterSquirrelFunc( v, SHA512, "SHA512", 1, "s" );
	RegisterSquirrelFunc( v, RIPEMD128, "RIPEMD128", 1, "s" );
	RegisterSquirrelFunc( v, RIPEMD160, "RIPEMD160", 1, "s" );
	RegisterSquirrelFunc( v, RIPEMD256, "RIPEMD256", 1, "s" );
	RegisterSquirrelFunc( v, RIPEMD320, "RIPEMD320", 1, "s" );
	RegisterSquirrelFunc( v, WHIRLPOOL, "WHIRLPOOL", 1, "s" );

	// Hash functions
}
